package logger

import (
	"fmt"
	"math"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// 日志库基础设定
const (
	_LOG_PATH_       = "logs/"
	_EXSTORAGE_PATH_ = ""
	_ARCHIVE_PATH_   = _EXSTORAGE_PATH_ + "/logs/"
	_MAX_LOG_BYTES_  = 1000 * 1000 * 10 // 10MB
	_TIME_LAYOUT_    = "20060102"
)

var (
	once   sync.Once
	logger *zap.Logger
)

func initLogger(filename string) *zap.Logger {

	encoderCfg := zap.NewProductionEncoderConfig()
	encoderCfg.EncodeTime = zapcore.RFC3339TimeEncoder
	encoderCfg.EncodeLevel = zapcore.CapitalLevelEncoder
	core := zapcore.NewCore(
		zapcore.NewConsoleEncoder(encoderCfg),
		zapcore.AddSync(&lumberjackSink{fileName: filename}),
		zap.DebugLevel,
	)

	logger = zap.New(core)

	return logger
}

type lumberjackSink struct {
	lastFileDate string
	file         *os.File
	writtenBytes int64
	initialized  bool
	fileName     string
}

func (l *lumberjackSink) Write(p []byte) (int, error) {
	if !l.initialized {
		l.init()
	}

	if l.file == nil || l.lastFileDate != time.Now().Format(_TIME_LAYOUT_) {
		l.rotate()
	}

	writeBytes, err := l.file.Write(p)
	l.writtenBytes += int64(writeBytes)

	if l.writtenBytes >= _MAX_LOG_BYTES_ {
		l.rotate()
	}

	return writeBytes, err
}

func (l *lumberjackSink) Sync() error {
	if l.file != nil {
		return l.file.Sync()
	}
	return nil
}

func (l *lumberjackSink) rotate() {
	if l.file != nil {
		err := l.file.Close()
		if err != nil {
			panic(fmt.Sprintf("Failed to close log file %s : %v", l.file.Name(), err))
		}

		//删除过期目录,U盘目录暂时通过ilog库一起删除
		DeleteOldLogDirectoriesMemory(_LOG_PATH_)

		// 创建目录
		backupPath := filepath.Join(_LOG_PATH_, l.lastFileDate)
		createDirectoryIfNotExists(backupPath)

		// 获取目录内容
		entries, err := os.ReadDir(backupPath)
		if err != nil {
			panic(fmt.Sprintf("Failed to read directory %s : %v", backupPath, err))
		}

		// 计算文件数量
		fileCnt := 0
		for _, entry := range entries {
			if !entry.IsDir() {
				fileCnt++
			}
		}

		curFile := fmt.Sprintf("%s_%s.log", l.fileName, l.lastFileDate)
		curFilePath := filepath.Join(_LOG_PATH_, curFile)
		backupFilePath := filepath.Join(backupPath, fmt.Sprintf("%s.%03d", curFile, fileCnt))
		err = os.Rename(curFilePath, backupFilePath)
		if err != nil {
			panic(fmt.Sprintf("Failed to rename log file %s to %s : %v", curFilePath, backupFilePath, err))
		}
	}

	// 重新初始化一个新文件
	l.lastFileDate = time.Now().Format(_TIME_LAYOUT_)
	l.writtenBytes = 0

	err := error(nil)
	newLogFile := filepath.Join(_LOG_PATH_, fmt.Sprintf("%s_%s.log", l.fileName, l.lastFileDate))
	l.file, err = os.OpenFile(newLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		panic(fmt.Sprintf("Failed to open log file %s : %v", newLogFile, err))
	}
}

func (l *lumberjackSink) init() {
	l.initialized = true
	l.lastFileDate = time.Now().Format(_TIME_LAYOUT_)

	// 创建目录
	createDirectoryIfNotExists(_LOG_PATH_)

	err := error(nil)

	// 初始化 {logPath}/{l.fileName}_{yyyyMMdd}.log 文件
	curLogFile := filepath.Join(_LOG_PATH_, fmt.Sprintf("%s_%s.log", l.fileName, l.lastFileDate))
	l.file, err = os.OpenFile(curLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		panic(fmt.Sprintf("Failed to open log file %s : %v", curLogFile, err))
	}

	stats, err := l.file.Stat()
	if err != nil {
		panic(fmt.Sprintf("Failed to get log file %s stats : %v", curLogFile, err))
	}
	l.writtenBytes = stats.Size()
}

func createDirectoryIfNotExists(dirPath string) {
	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
		err := os.MkdirAll(dirPath, 0755)
		if err != nil {
			panic(fmt.Sprintf("Failed to create directory %s: %v", dirPath, err))
		}
	}
}

// 清理内存过期日志
func DeleteOldLogDirectoriesMemory(path string) {
	currentDate := time.Now()

	files, err := os.ReadDir(path)
	if err == nil {
		for _, file := range files {
			if file.IsDir() {
				dirDate, err := time.Parse(_TIME_LAYOUT_, file.Name())
				timeDiff := math.Abs(currentDate.Sub(dirDate).Hours())
				if err == nil && timeDiff > 48 {
					fullPath := filepath.Join(path, file.Name())
					os.RemoveAll(fullPath)
				}
			}
		}
	}
}

func InitLogger(logName string) {
	once.Do(func() {
		logger = initLogger(logName)
	})
	Info("logger init success")
}

func GetLogger() *zap.Logger {
	if logger == nil {
		InitLogger("log")
	}
	return logger
}

func Info(msg string, keysAndValues ...interface{}) {
	if len(keysAndValues) == 0 {
		GetLogger().Info(msg)
	} else {
		GetLogger().Sugar().Infof(msg, keysAndValues...)
	}
}

func Debug(msg string, keysAndValues ...interface{}) {
	if len(keysAndValues) == 0 {
		GetLogger().Debug(msg)
	} else {
		GetLogger().Sugar().Debugf(msg, keysAndValues...)
	}
}

func Warn(msg string, keysAndValues ...interface{}) {
	if len(keysAndValues) == 0 {
		GetLogger().Warn(msg)
	} else {
		GetLogger().Sugar().Warnf(msg, keysAndValues...)
	}
}

func Error(msg string, keysAndValues ...interface{}) {
	if len(keysAndValues) == 0 {
		GetLogger().Error(msg)
	} else {
		GetLogger().Sugar().Errorf(msg, keysAndValues...)
	}
}

func Fatal(msg string, keysAndValues ...interface{}) {
	if len(keysAndValues) == 0 {
		GetLogger().Fatal(msg)
	} else {
		GetLogger().Sugar().Fatalf(msg, keysAndValues...)
	}
}

func CheckExstorage() bool {
	out, err := exec.Command("df").Output()
	if err != nil {
		fmt.Println(err)
		return false
	}

	lines := strings.Split(string(out), "\n")
	for _, line := range lines {
		if strings.Contains(line, _EXSTORAGE_PATH_) {
			return true
		}
	}

	return false
}
